package com.apex.lottery.service.mq;


import com.apex.lottery.common.exception.ServiceException;
import com.apex.lottery.common.utils.JacksonUtil;
import com.apex.lottery.controller.param.DrawPrizeParam;
import com.apex.lottery.dao.dataobject.ActivityPrizeDO;
import com.apex.lottery.dao.dataobject.WinningRecordDO;
import com.apex.lottery.dao.mapper.ActivityPrizeMapper;
import com.apex.lottery.dao.mapper.WinningRecordMapper;
import com.apex.lottery.service.DrawPrizeService;
import com.apex.lottery.service.enums.ActivityPrizeStatusEnum;
import com.apex.lottery.service.enums.ActivityStatusEnum;
import com.apex.lottery.service.enums.ActivityUserStatusEnum;
import com.apex.lottery.service.strategy.activitystatus.ActivityStatusManager;
import com.apex.lottery.service.dto.ConvertActivityStatusDTO;
import com.apex.lottery.service.strategy.execution.SendMailStrategy;
import com.apex.lottery.service.strategy.execution.SendMessageStrategy;
import com.apex.lottery.service.strategy.execution.TaskExecutionStrategy;
import com.apex.lottery.service.strategy.execution.TaskExecutorContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.apex.lottery.common.constant.Constants.QUEUE_NAME;

/**
 * @ClassName MqConsumer
 * @Description
 * @Author ZJX
 * @Date 2025/4/20 20:29
 * @Version 1.0
 **/
@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqConsumer {
    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Qualifier("asyncServiceExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    private static final Logger logger = LoggerFactory.getLogger(MqConsumer.class);

    @RabbitHandler
    public void process(Map<String, String> message) throws Exception {
        // 成功接收到队列中的消息
        logger.info("MQ成功接收到消息，message:{}",
                JacksonUtil.writeValueAsString(message));

        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        // 处理抽奖的流程
        try {
//        校验抽奖请求是否有效
            if (!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }

//        状态扭转处理（重要！！ 设计模式）
            statusConvert(param);

//        保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);

//        通知中奖者(邮箱 短信)
            syncExecute(winningRecordDOList);

        } catch (ServiceException e){
//            发生异常  进行事务一致性(回滚) 抛出异常
            logger.error("处理 MQ 消息异常！{}:{}", e.getCode(), e.getMessage(), e);
            rollback(param);
        } catch (Exception e){
            logger.error("处理 MQ 消息异常！",  e);
            rollback(param);
            throw e;
        }
    }


    /**
     * 处理抽奖异常的回滚行为：恢复处理请求之前的库表状态
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
//        先判断是否需要回滚 不需要的话 直接 return
        if (!statusNeedRollback(param)){
            return;
        }
//        回滚状态: 活动 人员 奖品
        rollbackStatus(param);

        if (!winnerNeedRollback(param)){
            return;
        }

//        回滚中奖者名单
        rollbackWinner(param);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
//        statusConvert 方法保证了事务一致性 所以判断其中一个状态是否扭转即可 (要么全扭转了，要么全没扭转)
        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        return activityPrizeDO.getStatus().equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name());
    }

    /**
     * 恢复相关状态
     *
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        int count =
                winningRecordMapper.countByAPId(param.getActivityId(), param.getPrizeId());
        return count > 0;
    }

    /**
     * 回滚中奖记录：删除奖品下的中奖者
     *
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }


    /**
     * 并发异步处理抽奖后续流程
     *
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        // 创建不同的策略
        TaskExecutionStrategy sendMessageStrategy = new SendMessageStrategy(threadPoolTaskExecutor);
        TaskExecutionStrategy sendMailStrategy = new SendMailStrategy(threadPoolTaskExecutor);

        // 创建上下文并执行任务
        TaskExecutorContext sendMessageContext = new TaskExecutorContext(sendMessageStrategy);
        TaskExecutorContext sendMailContext = new TaskExecutorContext(sendMailStrategy);

        // 执行任务
        sendMessageContext.execute(winningRecordDOList);
        sendMailContext.execute(winningRecordDOList);
    }

    /**
     * 状态扭转
     *
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }

}
